﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Engine.Dtd.Schema;
using System.Diagnostics;

namespace Engine.Sgml.Matching {
    internal sealed class ModelMatcher {
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private AnyOrderModelMatcher anyOrderMatcher;
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private SequenceModelMatcher sequenceMatcher;
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private OnlyOneModelMatcher onlyOneMatcher;

        public ModelMatcher() {
            this.anyOrderMatcher = new AnyOrderModelMatcher(this);
            this.sequenceMatcher = new SequenceModelMatcher(this);
            this.onlyOneMatcher = new OnlyOneModelMatcher(this);
            }

        public IList<Capture> Match(SElement node) {
            Guard.ArgumentNotNull(node, "node");
            CaptureCollection captures = new CaptureCollection();
            if (node.Declaration == null) {
                // If the node doesn't have a DTD declaration in a current scheme
                // or if it is a text or charachter data
                captures.Add(new Match(DModelGroups.Empty, node, null), false);
                for (int i = 0; i < node.Nodes.Count; i++)
                    captures.Add(node.Nodes[i]);
                }
            else {
                switch (node.Declaration.ContentModel.ContentType) {
                    case DContentModelType.Default:
                    case DContentModelType.CData:
                        SElement element = node.FirstNode;
                        DContentModel contentModel = node.Declaration.ContentModel;
                        while (element != null) {
                            Match match = new Match(contentModel.Groups, node, null);
                            Match(match, ref element, contentModel);
                            if (match.Captures.Any(c => c.Success)) {
                                captures.Add(match, true);
                                }
                            else {
                                captures.Add(element);
                                element = element.NextNode;
                                }
                            }
                        if (captures.Count == 0 || captures.All(c => c.Entity.IsIllegalElement)) {
                            Match match = new Match(contentModel.Groups, node, null);
                            MatchFinalizer.Finalize(match);
                            captures.Insert(0, new Capture(match, false));
                            }
                        break;
                    case DContentModelType.RCData:
                        break;
                    case DContentModelType.Empty:
                        captures.Add(new Match(node.Declaration.ContentModel.Groups, node, null), false);
                        for (int i = 0; i < node.Nodes.Count; i++)
                            captures.Add(node.Nodes[i]);
                        break;
                    default:
                        throw new InvalidOperationException("Unknown the content type of the content model.");
                    }
                }
            Debug.Assert(captures.Count > 0);
            Debug.Assert(captures.All(c => c.Entity.IsModelMatch ? c.Entity.Match.Parent == null : true));
            Debug.Assert(captures.Any(c => c.Entity.IsModelMatch), "The capture collection doesn't contain any model groups match element.");
            Debug.Assert(captures.All(c => c.Entity.IsModelMatch || c.Entity.IsIllegalElement));
            Debug.Assert(captures.First().StartIndex == 0);
            Debug.Assert(captures.Last().EndIndex == (node.Nodes.Count == 0 ? 0 : node.Nodes.Count - 1));
            Debug.Assert(captures.All(c => c.Entity.IsModelMatch ? MatchTestUtility.Verify(c.Entity.Match) : true));
            return captures;
            }

        internal void Match(Match match, ref SElement element, DContentModel contentModel) {
            if (!contentModel.Exclusions.Contains(element.DName)) {
                switch (match.Model.OrderType) {
                    case DGroupOrderType.OnlyOne:
                        onlyOneMatcher.Match(ref match, ref element, contentModel);
                        break;
                    case DGroupOrderType.Sequence:
                        sequenceMatcher.Match(ref match, ref element, contentModel);
                        break;
                    case DGroupOrderType.AnyOrder:
                        anyOrderMatcher.Match(ref match, ref element, contentModel);
                        break;
                    default:
                        throw new InvalidOperationException("Unknown an order type of the model groups object.");
                    }
                }
            }

        internal void MatchRepetitive(Match parent, DModelGroups model, ref SElement element, DContentModel contentModel) {
            Debug.Assert(parent.HasMatches);
            Debug.Assert(parent.Captures.Last().Entity.Model == model);
            if (element != null && model.CanRecur())
                do {
                    Match repetition = new Match(model, parent.Ancestor, parent);
                    Match(repetition, ref element, contentModel);
                    if (repetition.HasMatches)
                        parent.Captures.Add(repetition, true);
                    else
                        break;
                    } while (element != null);
            }

        internal static void MatchInclusions(Match match, ref SElement element, DContentModel contentModel) {
            while (element != null && contentModel.Inclusions.Contains(element.DName)) {
                match.Captures.Add(InclusionNode.Instance, element);
                element = element.NextNode;
                }
            }
        }
    }
